// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __MDF_ERR_H__
#define __MDF_ERR_H__

#include "esp_err.h"
#include "esp_log.h"

#ifdef __cplusplus
extern "C" {
#endif /**< _cplusplus */

typedef int32_t mdf_err_t;

/* Definitions for error constants. */

/* Definitions for error constants. */
#define MDF_OK                      0        /**< mdf_err_t value indicating success (no error) */
#define MDF_FAIL                    -1       /**< Generic mdf_err_t code indicating failure */

#define MDF_ERR_NO_MEM              0x100001 /**< Out of memory */
#define MDF_ERR_INVALID_ARG         0x100002 /**< Invalid argument */
#define MDF_ERR_INVALID_STATE       0x100003 /**< Invalid state */
#define MDF_ERR_INVALID_SIZE        0x100004 /**< Invalid size */
#define MDF_ERR_NOT_FOUND           0x100005 /**< Requested resource not found */
#define MDF_ERR_NOT_SUPPORTED       0x100006 /**< Operation or feature not supported */
#define MDF_ERR_TIMEOUT             0x100007 /**< Operation timed out */
#define MDF_ERR_INVALID_RESPONSE    0x100008 /**< Received response was invalid */
#define MDF_ERR_INVALID_CRC         0x100009 /**< CRC or checksum was invalid */
#define MDF_ERR_INVALID_VERSION     0x10000A /**< Version was invalid */
#define MDF_ERR_INVALID_MAC         0x10000B /**< MAC address was invalid */
#define MDF_ERR_NOT_INIT            0x10000C /**< MAC address was invalid */
#define MDF_ERR_BUF                 0x10000D /**< The buffer is too small */

#define MDF_ERR_MWIFI_BASE          0x200000 /**< Starting number of MWIFI error codes */
#define MDF_ERR_MESPNOW_BASE        0x300000 /**< Starting number of MESPNOW error codes */
#define MDF_ERR_MCONFIG_BASE        0x400000 /**< Starting number of MCONFIG error codes */
#define MDF_ERR_MUPGRADE_BASE       0x500000 /**< Starting number of MUPGRADE error codes */
#define MDF_ERR_MDEBUG_BASE         0x600000 /**< Starting number of MDEBUG error codes */
#define MDF_ERR_MLINK_BASE          0x700000 /**< Starting number of MLINK error codes */
#define MDF_ERR_CUSTOM_BASE         0x800000 /**< Starting number of COUSTOM error codes */

/**
  * @brief Returns string for mdf_err_t error codes
  *
  * This function finds the error code in a pre-generated lookup-table and
  * returns its string representation.
  *
  * The function is generated by the Python script
  * tools/gen_mdf_err_to_name.py which should be run each time an mdf_err_t
  * error is modified, created or removed from the IDF project.
  *
  * @param code mdf_err_t error code
  * @return string error message
  */
const char *mdf_err_to_name(mdf_err_t code);

#ifndef CONFIG_MDF_LOG_LEVEL
#define CONFIG_MDF_LOG_LEVEL ESP_LOG_DEBUG
#endif /**< CONFIG_MDF_LOG_LEVEL */
#define MDF_LOG_LEVEL CONFIG_MDF_LOG_LEVEL

#define MDF_LOG_FORMAT(letter, format)  LOG_COLOR_ ## letter #letter " (%u) [%s, %d]: " format LOG_RESET_COLOR "\n"

#define MDF_LOGE( format, ... ) do { \
        if (MDF_LOG_LEVEL >= ESP_LOG_ERROR) { \
            esp_log_write(ESP_LOG_ERROR, TAG, MDF_LOG_FORMAT(E, format), esp_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define MDF_LOGW( format, ... ) do { \
        if (MDF_LOG_LEVEL >= ESP_LOG_WARN) { \
            esp_log_write(ESP_LOG_WARN, TAG, MDF_LOG_FORMAT(W, format), esp_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define MDF_LOGI( format, ... ) do { \
        if (MDF_LOG_LEVEL >= ESP_LOG_INFO) { \
            esp_log_write(ESP_LOG_INFO, TAG, MDF_LOG_FORMAT(I, format), esp_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define MDF_LOGD( format, ... ) do { \
        if (MDF_LOG_LEVEL >= ESP_LOG_DEBUG) { \
            esp_log_write(ESP_LOG_DEBUG, TAG, MDF_LOG_FORMAT(D, format), esp_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define MDF_LOGV( format, ... ) do { \
        if (MDF_LOG_LEVEL >= ESP_LOG_VERBOSE) { \
            esp_log_write(ESP_LOG_VERBOSE, TAG, MDF_LOG_FORMAT(V, format), esp_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)


/**
 * Macro which can be used to check the error code,
 * and terminate the program in case the code is not MDF_OK.
 * Prints the error code, error location, and the failed statement to serial output.
 *
 * Disabled if assertions are disabled.
 */
#define MDF_ERROR_CHECK(con, err, format, ...) do { \
        if (con) { \
            if(*format != '\0') \
                MDF_LOGW("<%s> " format, mdf_err_to_name(err), ##__VA_ARGS__); \
            return err; \
        } \
    } while(0)

/**
 * @brief Macro serves similar purpose as ``assert``, except that it checks `esp_err_t`
 *        value rather than a `bool` condition. If the argument of `MDF_ERROR_ASSERT`
 *        is not equal `MDF_OK`, then an error message is printed on the console,
 *         and `abort()` is called.
 *
 * @note If `IDF monitor` is used, addresses in the backtrace will be converted
 *       to file names and line numbers.
 *
 * @param  err [description]
 * @return     [description]
 */
#define MDF_ERROR_ASSERT(err) do { \
        mdf_err_t __err_rc = (err); \
        if (__err_rc != MDF_OK) { \
            MDF_LOGW("<%s> MDF_ERROR_ASSERT failed, at 0x%08x, expression: %s", \
                     mdf_err_to_name(__err_rc), (intptr_t)__builtin_return_address(0) - 3, __ASSERT_FUNC); \
            assert(0 && #err); \
        } \
    } while(0)

#define MDF_ERROR_GOTO(con, lable, format, ...) do { \
        if (con) { \
            if(*format != '\0') \
                MDF_LOGW(format, ##__VA_ARGS__); \
            goto lable; \
        } \
    } while(0)

#define MDF_ERROR_CONTINUE(con, format, ...) { \
        if (con) { \
            if(*format != '\0') \
                MDF_LOGW(format, ##__VA_ARGS__); \
            continue; \
        } \
    }

#define MDF_ERROR_BREAK(con, format, ...) { \
        if (con) { \
            if(*format != '\0') \
                MDF_LOGW(format, ##__VA_ARGS__); \
            break; \
        } \
    }

#define MDF_PARAM_CHECK(con) do { \
        if (!(con)) { \
            MDF_LOGE("<MDF_ERR_INVALID_ARG> !(%s)", #con); \
            return MDF_ERR_INVALID_ARG; \
        } \
    } while(0)

#ifdef __cplusplus
}
#endif /**< _cplusplus */
#endif /**< __MDF_ERR_H__ */
